home *** CD-ROM | disk | FTP | other *** search
/ Aminet 40 / Aminet 40 (2000)(Schatztruhe)[!][Dec 2000].iso / Aminet / dev / lang / Python16.lha / Python-1.6 / Lib / Python1.6 / distutils / command / bdist_rpm.py < prev    next >
Encoding:
Python Source  |  2000-08-15  |  16.7 KB  |  483 lines

  1. """distutils.command.bdist_rpm
  2.  
  3. Implements the Distutils 'bdist_rpm' command (create RPM source and binary
  4. distributions)."""
  5.  
  6. # created 2000/04/25, by Harry Henry Gebel
  7.  
  8. __revision__ = "$Id: bdist_rpm.py,v 1.17 2000/08/15 13:05:35 gward Exp $"
  9.  
  10. import os, string
  11. import glob
  12. from types import *
  13. from distutils.core import Command, DEBUG
  14. from distutils.util import get_platform
  15. from distutils.file_util import write_file
  16. from distutils.errors import *
  17.  
  18. class bdist_rpm (Command):
  19.  
  20.     description = "create an RPM distribution"
  21.  
  22.     user_options = [
  23.         ('bdist-base=', None,
  24.          "base directory for creating built distributions"),
  25.         ('rpm-base=', None,
  26.          "base directory for creating RPMs (defaults to \"rpm\" under "
  27.          "--bdist-base; must be specified for RPM 2)"),
  28.         ('dist-dir=', 'd',
  29.          "directory to put final RPM files in "
  30.          "(and .spec files if --spec-only)"),
  31.         ('spec-only', None,
  32.          "only regenerate spec file"),
  33.         ('source-only', None,
  34.          "only generate source RPM"),
  35.         ('binary-only', None,
  36.          "only generate binary RPM"),
  37.         ('use-bzip2', None,
  38.          "use bzip2 instead of gzip to create source distribution"),
  39.  
  40.         # More meta-data: too RPM-specific to put in the setup script,
  41.         # but needs to go in the .spec file -- so we make these options
  42.         # to "bdist_rpm".  The idea is that packagers would put this
  43.         # info in setup.cfg, although they are of course free to
  44.         # supply it on the command line.
  45.         ('distribution-name', None,
  46.          "name of the (Linux) distribution name to which this "
  47.          "RPM applies (*not* the name of the module distribution!)"),
  48.         ('group', None,
  49.          "package classification [default: \"Development/Libraries\"]"),
  50.         ('release', None,
  51.          "RPM release number"),
  52.         ('serial', None,
  53.          "RPM serial number"),
  54.         ('vendor', None,
  55.          "RPM \"vendor\" (eg. \"Joe Blow <joe@example.com>\") "
  56.          "[default: maintainer or author from setup script]"),
  57.         ('packager', None,
  58.          "RPM packager (eg. \"Jane Doe <jane@example.net>\")"
  59.          "[default: vendor]"),
  60.         ('doc-files', None,
  61.          "list of documentation files (space or comma-separated)"),
  62.         ('changelog', None,
  63.          "path to RPM changelog"),
  64.         ('icon', None,
  65.          "name of icon file"),
  66.         ('prep-script', None,
  67.          "pre-build script  (Bourne shell code)"),
  68.         ('build-script', None,
  69.          "build script (Bourne shell code)"),
  70.         ('install-script', None,
  71.          "installation script (Bourne shell code)"),
  72.         ('clean-script', None,
  73.          "clean script (Bourne shell code)"),
  74.         ('pre-install', None,
  75.          "pre-install script (Bourne shell code)"),
  76.         ('post-install', None,
  77.          "post-install script (Bourne shell code)"),
  78.         ('pre-uninstall', None,
  79.          "pre-uninstall script (Bourne shell code)"),
  80.         ('post-uninstall', None,
  81.          "post-uninstall script (Bourne shell code)"),
  82.         ('provides', None,
  83.          "capabilities provided by this package"),
  84.         ('requires', None,
  85.          "capabilities required by this package"),
  86.         ('conflicts', None,
  87.          "capabilities which conflict with this package"),
  88.         ('build-requires', None,
  89.          "capabilities required to build this package"),
  90.         ('obsoletes', None,
  91.          "capabilities made obsolete by this package"),
  92.  
  93.         # Actions to take when building RPM
  94.         ('clean', None,
  95.          "clean up RPM build directory [default]"),
  96.         ('no-clean', None,
  97.          "don't clean up RPM build directory"),
  98.         ('use-rpm-opt-flags', None,
  99.          "compile with RPM_OPT_FLAGS when building from source RPM"),
  100.         ('no-rpm-opt-flags', None,
  101.          "do not pass any RPM CFLAGS to compiler"),
  102.         ('rpm3-mode', None,
  103.          "RPM 3 compatibility mode (default)"),
  104.         ('rpm2-mode', None,
  105.          "RPM 2 compatibility mode"),
  106.        ]
  107.  
  108.     negative_opt = {'no-clean': 'clean',
  109.                     'no-rpm-opt-flags': 'use-rpm-opt-flags',
  110.                     'rpm2-mode': 'rpm3-mode'}
  111.  
  112.                     
  113.     def initialize_options (self):
  114.         self.bdist_base = None
  115.         self.rpm_base = None
  116.         self.dist_dir = None
  117.         self.spec_only = None
  118.         self.binary_only = None
  119.         self.source_only = None
  120.         self.use_bzip2 = None
  121.  
  122.         self.distribution_name = None
  123.         self.group = None
  124.         self.release = None
  125.         self.serial = None
  126.         self.vendor = None
  127.         self.packager = None
  128.         self.doc_files = None
  129.         self.changelog = None
  130.         self.icon = None
  131.  
  132.         self.prep_script = None
  133.         self.build_script = None
  134.         self.install_script = None
  135.         self.clean_script = None
  136.         self.pre_install = None
  137.         self.post_install = None
  138.         self.pre_uninstall = None
  139.         self.post_uninstall = None
  140.         self.prep = None
  141.         self.provides = None
  142.         self.requires = None
  143.         self.conflicts = None
  144.         self.build_requires = None
  145.         self.obsoletes = None
  146.  
  147.         self.clean = 1
  148.         self.use_rpm_opt_flags = 1
  149.         self.rpm3_mode = 1
  150.  
  151.     # initialize_options()
  152.  
  153.  
  154.     def finalize_options (self):
  155.         self.set_undefined_options('bdist', ('bdist_base', 'bdist_base'))
  156.         if self.rpm_base is None:
  157.             if not self.rpm3_mode:
  158.                 raise DistutilsOptionError, \
  159.                       "you must specify --rpm-base in RPM 2 mode"
  160.             self.rpm_base = os.path.join(self.bdist_base, "rpm")
  161.  
  162.         if os.name != 'posix':
  163.             raise DistutilsPlatformError, \
  164.                   ("don't know how to create RPM "
  165.                    "distributions on platform %s" % os.name)
  166.         if self.binary_only and self.source_only:
  167.             raise DistutilsOptionsError, \
  168.                   "cannot supply both '--source-only' and '--binary-only'"
  169.  
  170.         # don't pass CFLAGS to pure python distributions
  171.         if not self.distribution.has_ext_modules():
  172.             self.use_rpm_opt_flags = 0
  173.  
  174.         self.set_undefined_options('bdist', ('dist_dir', 'dist_dir'))
  175.         self.finalize_package_data()
  176.  
  177.     # finalize_options()
  178.  
  179.     def finalize_package_data (self):
  180.         self.ensure_string('group', "Development/Libraries")
  181.         self.ensure_string('vendor',
  182.                            "%s <%s>" % (self.distribution.get_contact(),
  183.                                         self.distribution.get_contact_email()))
  184.         self.ensure_string('packager') 
  185.         self.ensure_string_list('doc_files')
  186.         if type(self.doc_files) is ListType:
  187.             for readme in ('README', 'README.txt'):
  188.                 if os.path.exists(readme) and readme not in self.doc_files:
  189.                     self.doc.append(readme)
  190.  
  191.         self.ensure_string('release', "1")
  192.         self.ensure_string('serial')   # should it be an int?
  193.  
  194.         self.ensure_string('distribution_name')
  195.  
  196.         self.ensure_string('changelog')
  197.           # Format changelog correctly
  198.         self.changelog = self._format_changelog(self.changelog)
  199.  
  200.         self.ensure_filename('icon')
  201.         
  202.         self.ensure_filename('prep_script')
  203.         self.ensure_filename('build_script')
  204.         self.ensure_filename('install_script')
  205.         self.ensure_filename('clean_script')
  206.         self.ensure_filename('pre_install')
  207.         self.ensure_filename('post_install')
  208.         self.ensure_filename('pre_uninstall')
  209.         self.ensure_filename('post_uninstall')
  210.  
  211.         # XXX don't forget we punted on summaries and descriptions -- they
  212.         # should be handled here eventually!
  213.  
  214.         # Now *this* is some meta-data that belongs in the setup script...
  215.         self.ensure_string_list('provides')
  216.         self.ensure_string_list('requires')
  217.         self.ensure_string_list('conflicts')
  218.         self.ensure_string_list('build_requires')
  219.         self.ensure_string_list('obsoletes')
  220.  
  221.     # finalize_package_data ()
  222.  
  223.  
  224.     def run (self):
  225.  
  226.         if DEBUG:
  227.             print "before _get_package_data():"
  228.             print "vendor =", self.vendor
  229.             print "packager =", self.packager
  230.             print "doc_files =", self.doc_files
  231.             print "changelog =", self.changelog
  232.  
  233.         # make directories
  234.         if self.spec_only:
  235.             spec_dir = self.dist_dir
  236.             self.mkpath(spec_dir)
  237.         else:
  238.             rpm_dir = {}
  239.             for d in ('SOURCES', 'SPECS', 'BUILD', 'RPMS', 'SRPMS'):
  240.                 rpm_dir[d] = os.path.join(self.rpm_base, d)
  241.                 self.mkpath(rpm_dir[d])
  242.             spec_dir = rpm_dir['SPECS']
  243.  
  244.         # Spec file goes into 'dist_dir' if '--spec-only specified',
  245.         # build/rpm.<plat> otherwise.
  246.         spec_path = os.path.join(spec_dir,
  247.                                  "%s.spec" % self.distribution.get_name())
  248.         self.execute(write_file,
  249.                      (spec_path,
  250.                       self._make_spec_file()),
  251.                      "writing '%s'" % spec_path)
  252.  
  253.         if self.spec_only: # stop if requested
  254.             return
  255.  
  256.         # Make a source distribution and copy to SOURCES directory with
  257.         # optional icon.
  258.         sdist = self.reinitialize_command ('sdist')
  259.         if self.use_bzip2:
  260.             sdist.formats = ['bztar']
  261.         else:
  262.             sdist.formats = ['gztar']
  263.         self.run_command('sdist')
  264.  
  265.         source = sdist.get_archive_files()[0]
  266.         source_dir = rpm_dir['SOURCES']
  267.         self.copy_file(source, source_dir)
  268.  
  269.         if self.icon:
  270.             if os.path.exists(self.icon):
  271.                 self.copy_file(self.icon, source_dir)
  272.             else:
  273.                 raise DistutilsFileError, \
  274.                       "icon file '%s' does not exist" % self.icon
  275.         
  276.  
  277.         # build package
  278.         self.announce('Building RPMs')
  279.         rpm_args = ['rpm',]
  280.         if self.source_only: # what kind of RPMs?
  281.             rpm_args.append('-bs')
  282.         elif self.binary_only:
  283.             rpm_args.append('-bb')
  284.         else:
  285.             rpm_args.append('-ba')
  286.         if self.rpm3_mode:
  287.             rpm_args.extend(['--define',
  288.                              '_topdir %s/%s' % (os.getcwd(), self.rpm_base),])
  289.         if self.clean:
  290.             rpm_args.append('--clean')
  291.         rpm_args.append(spec_path)
  292.         self.spawn(rpm_args)
  293.  
  294.         # XXX this is a nasty hack -- we really should have a proper way to
  295.         # find out the names of the RPM files created; also, this assumes
  296.         # that RPM creates exactly one source and one binary RPM.
  297.         if not self.dry_run:
  298.             if not self.binary_only:
  299.                 srpms = glob.glob(os.path.join(rpm_dir['SRPMS'], "*.rpm"))
  300.                 assert len(srpms) == 1, \
  301.                        "unexpected number of SRPM files found: %s" % srpms
  302.                 self.move_file(srpms[0], self.dist_dir)
  303.  
  304.             if not self.source_only:
  305.                 rpms = glob.glob(os.path.join(rpm_dir['RPMS'], "*/*.rpm"))
  306.                 assert len(rpms) == 1, \
  307.                        "unexpected number of RPM files found: %s" % rpms
  308.                 self.move_file(rpms[0], self.dist_dir)
  309.  
  310.     # run()
  311.  
  312.  
  313.     def _make_spec_file(self):
  314.         """Generate the text of an RPM spec file and return it as a
  315.         list of strings (one per line).
  316.         """
  317.         # definitions and headers
  318.         spec_file = [
  319.             '%define name ' + self.distribution.get_name(),
  320.             '%define version ' + self.distribution.get_version(),
  321.             '%define release ' + self.release,
  322.             '',
  323.             'Summary: ' + self.distribution.get_description(),
  324.             ]
  325.  
  326.         # put locale summaries into spec file
  327.         # XXX not supported for now (hard to put a dictionary
  328.         # in a config file -- arg!)
  329.         #for locale in self.summaries.keys():
  330.         #    spec_file.append('Summary(%s): %s' % (locale,
  331.         #                                          self.summaries[locale]))
  332.  
  333.         spec_file.extend([
  334.             'Name: %{name}',
  335.             'Version: %{version}',
  336.             'Release: %{release}',])
  337.  
  338.         # XXX yuck! this filename is available from the "sdist" command,
  339.         # but only after it has run: and we create the spec file before
  340.         # running "sdist", in case of --spec-only.
  341.         if self.use_bzip2:
  342.             spec_file.append('Source0: %{name}-%{version}.tar.bz2')
  343.         else:
  344.             spec_file.append('Source0: %{name}-%{version}.tar.gz')
  345.  
  346.         spec_file.extend([
  347.             'Copyright: ' + self.distribution.get_licence(),
  348.             'Group: ' + self.group,
  349.             'BuildRoot: %{_tmppath}/%{name}-buildroot',
  350.             'Prefix: %{_prefix}', ])
  351.  
  352.         # noarch if no extension modules
  353.         if not self.distribution.has_ext_modules():
  354.             spec_file.append('BuildArchitectures: noarch')
  355.  
  356.         for field in ('Vendor',
  357.                       'Packager',
  358.                       'Provides',
  359.                       'Requires',
  360.                       'Conflicts',
  361.                       'Obsoletes',
  362.                       ):
  363.             val = getattr(self, string.lower(field))
  364.             if type(val) is ListType:
  365.                 spec_file.append('%s: %s' % (field, string.join(val)))
  366.             elif val is not None:
  367.                 spec_file.append('%s: %s' % (field, val))
  368.                 
  369.                       
  370.         if self.distribution.get_url() != 'UNKNOWN':
  371.             spec_file.append('Url: ' + self.distribution.get_url())
  372.  
  373.         if self.distribution_name:
  374.              spec_file.append('Distribution: ' + self.distribution_name)
  375.  
  376.         if self.build_requires:
  377.              spec_file.append('BuildRequires: ' +
  378.                               string.join(self.build_requires))
  379.  
  380.         if self.icon:
  381.             spec_file.append('Icon: ' + os.path.basename(self.icon))
  382.  
  383.         spec_file.extend([
  384.             '',
  385.             '%description',
  386.             self.distribution.get_long_description()
  387.             ])
  388.  
  389.         # put locale descriptions into spec file
  390.         # XXX again, suppressed because config file syntax doesn't
  391.         # easily support this ;-(
  392.         #for locale in self.descriptions.keys():
  393.         #    spec_file.extend([
  394.         #        '',
  395.         #        '%description -l ' + locale,
  396.         #        self.descriptions[locale],
  397.         #        ])
  398.  
  399.         # rpm scripts
  400.         # figure out default build script
  401.         if self.use_rpm_opt_flags:
  402.             def_build = 'env CFLAGS="$RPM_OPT_FLAGS" python setup.py build'
  403.         else:
  404.             def_build = 'python setup.py build'
  405.         # insert contents of files
  406.  
  407.         # XXX this is kind of misleading: user-supplied options are files
  408.         # that we open and interpolate into the spec file, but the defaults
  409.         # are just text that we drop in as-is.  Hmmm.
  410.  
  411.         script_options = [
  412.             ('prep', 'prep_script', "%setup"),
  413.             ('build', 'build_script', def_build),
  414.             ('install', 'install_script',
  415.              "python setup.py install "
  416.              "--root=$RPM_BUILD_ROOT "
  417.              "--record=INSTALLED_FILES"),
  418.             ('clean', 'clean_script', "rm -rf $RPM_BUILD_ROOT"),
  419.             ('pre', 'pre_install', None),
  420.             ('post', 'post_install', None),
  421.             ('preun', 'pre_uninstall', None),
  422.             ('postun', 'post_uninstall', None),
  423.         ]
  424.  
  425.         for (rpm_opt, attr, default) in script_options:
  426.             # Insert contents of file referred to, if no file is refered to
  427.             # use 'default' as contents of script
  428.             val = getattr(self, attr)
  429.             if val or default:
  430.                 spec_file.extend([
  431.                     '',
  432.                     '%' + rpm_opt,])
  433.                 if val:
  434.                     spec_file.extend(string.split(open(val, 'r').read(), '\n'))
  435.                 else:
  436.                     spec_file.append(default)
  437.  
  438.  
  439.         # files section
  440.         spec_file.extend([
  441.             '',
  442.             '%files -f INSTALLED_FILES',
  443.             '%defattr(-,root,root)',
  444.             ])
  445.  
  446.         if self.doc_files:
  447.             spec_file.append('%doc ' + string.join(self.doc_files))
  448.  
  449.         if self.changelog:
  450.             spec_file.extend([
  451.                 '',
  452.                 '%changelog',])
  453.             spec_file.extend(self.changelog)
  454.  
  455.         return spec_file
  456.  
  457.     # _make_spec_file ()
  458.  
  459.     def _format_changelog(self, changelog):
  460.         """Format the changelog correctly and convert it to a list of strings
  461.         """
  462.         if not changelog:
  463.             return changelog
  464.         new_changelog = []
  465.         for line in string.split(string.strip(changelog), '\n'):
  466.             line = string.strip(line)
  467.             if line[0] == '*':
  468.                 new_changelog.extend(['', line])
  469.             elif line[0] == '-':
  470.                 new_changelog.append(line)
  471.             else:
  472.                 new_changelog.append('  ' + line)
  473.                 
  474.         # strip trailing newline inserted by first changelog entry
  475.         if not new_changelog[0]:
  476.             del new_changelog[0]
  477.         
  478.         return new_changelog
  479.  
  480.     # _format_changelog()
  481.  
  482. # class bdist_rpm
  483.